--[[
	ReaLua examples to get you started scripting
	REAPER with Lua!

	Load 'em up with

	dofile("w:\\ReaLua\\trunk\\examples.lua")

	substituting of course the path to this file on your system,
	and using CTRL-Enter to enter the line to the Lua interpreter.

	Then run them the usual Lua way, usuallualy something like

	tt = TracksTable()

	The long comments that begin with the "dofile()" are meant
	to be copied and pasted into the ReaLua Window Input Area.

]]

-- Some helper functions:

-- sprint(...) helper function does QnD
-- mock print(...) to a string:

function sprint(...)
  local oot = ""
  for i,v in ipairs(arg) do
    oot = oot .. tostring(v)
    if i ~= arg.n then
      oot = oot .. "\t"
    end
  end
  oot = oot .. "\r\n"
  return oot
end

-- ordered pairs({})

function opairs(t)
	local a = {}	-- table of indexes to sort
	for n in pairs(t) do table.insert(a, n) end
	table.sort(a)
	local i = 0
	return function ()
		i = i + 1
		if a[i] == nil then return nil
		else return a[i], t[a[i]]
		end
	end
end

function ptable(t)
	for k,v in opairs(t) do
		print(k,v)
	end
end

-- iterate over all tracks, possibly including master

local function ialltrax(bmaster)
	local bmaster = bmaster or false
	local nt = reaper.CSurf_NumTracks(bmaster)
	local idx = 0	-- start at one
	if bmaster == true then idx = -1 end	-- start at zero
	return function()
		idx = idx + 1
		if idx <= nt then
			return reaper.CSurf_TrackFromID(idx,bmaster)
		end
	end
end

--[[
	trax(optional selornam, optional incmaster)

	Pick Tracks, possibly including the master, and return
	them in a table.

	If selornam is not passed or is false, return all tracks
	If selornam is true, return all selected tracks

	If selornam is a string, return all tracks with a name
	that includes the string (regardless of selection-state)

	If selornam is a table, return all tracks with names or
	numbers in the list (again regardless of selection-state)

	If incmaster is not passed or is false, do not include the master track;
	if incmaster is true, include the master track in the search.
]]

function trax(selornam, incmaster)

	-- boolean selection
	local function btrax(onlysel, bmaster)
		local all = not(onlysel or false)
		local trt = {}
		for mt in ialltrax(bmaster) do
			local sel = reaper.MediaTrackInfo.GetSelected(mt) == 1
			if all or sel then
				table.insert(trt, mt)
			end
		end
		return trt
	end

	-- string selection (ignore case!)
	local function strax(str, bmaster)
		local trt = {}
		for mt in ialltrax(bmaster) do
			local ns = reaper.MediaTrackInfo.GetName(mt) or "MASTER"
			if string.find(ns:upper(), str:upper()) then
				table.insert(trt, mt)
			end
		end
		return trt
	end

	-- single number selection
	local function ntrax(trn, bmaster)
		local bmaster = bmaster or false
		return reaper.CSurf_TrackFromID(trn, bmaster)
	end

	-- list selection
	local function ltrax(selornam, bmaster)
		local trt = {}
		local atb = {}
		for k,v in opairs(selornam) do
			if type(v) == "string" then
				atb = strax(v, bmaster)
			elseif type(v) == "number" then
				atb = { ntrax(v, bmaster) }
			end
			for l,w in opairs(atb) do
				table.insert(trt, w)
			end
		end
		return trt
	end

	-- determine the flavor of function to call
	if type(selornam) == "boolean" then
		return btrax(selornam, incmaster)
	elseif type(selornam) == "nil" then
		return btrax()
	elseif type(selornam) == "string" then
		return strax(selornam, incmaster)
	elseif type(selornam) == "table" then
		return ltrax(selornam, incmaster)
	end
end

--[[	Quick record arm/clear demostrate power of trax():

	rac({3},true)	arms track 3
	ra {2,3,4}		arms tracks 2,3,4
	rc "piano"		clears rec arm on all tracks with "piano" in them
]]

function rac(lit, bac)
	for k,v in next, trax(lit) do reaper.CSurf_OnRecArmChange(v, bac) end
end
function ra(lit) rac(lit, true) end
function rc(lit) rac(lit, false) end

--[[
		Collect data into Lua
		tables for display, file
		write, whatever::
]]

-- all the params for the fx in one table
-- each element is { name, curval }

function FXParamsTable(mt, fxn)
	local pt = {}
	for j = 0, reaper.TrackFX_GetNumParams(mt, fxn) do
		local ns = reaper.TrackFX_GetParamName(mt, fxn, j)
		if ns then
			local curv = reaper.TrackFX_GetParam(mt, fxn, j)
			pt[j] = { name = ns, curval = curv }
		end
	end

	return pt
end

-- all the FX for the track in one table
-- each element is { name, id, {params} }

function TrackFXTable(mt)
	local fxt = {}
	for j=0, reaper.TrackFX_GetCount(mt) do
		local ns = reaper.TrackFX_GetFXName(mt, j)
		if ns then
			fxt[j] = {
				name = ns, id = j,
				params = FXParamsTable(mt, j)
			}
--			ptable(fxt[j])
		end
	end
	return fxt
end

-- the vital specifics for each track
-- { name, id, volume, pan, {fx} }

function TrackVitals(mt)
	local ns = reaper.MediaTrackInfo.GetName(mt) or "MASTER"
	local vol = reaper.MediaTrackInfo.GetVol(mt)
	local panv = reaper.MediaTrackInfo.GetPan(mt)
	local tn = reaper.CSurf_TrackToID(mt, true)
	return
		{	name = ns, id = tn, volume = vol, pan = panv,
			fx = TrackFXTable(mt)
		}
end

-- all the tracks vitals in one table

function TracksTable()
	local tracks = {}
	for mt in ialltrax(true) do
		table.insert(tracks, TrackVitals(mt))
	end
	return tracks
end

--[[
		A single print() happens in computer time
		Repeated print()s happen in human time
		Therefore if the slowness of print()s is
		a problem, just gang up the strings
		and print() it all at once at the end
]]

function PrintTracksTable(tt)
	local s = ""
	for k,tr in opairs(tt) do
		s = s .. sprint( "Track: ", tr.id, tr.name,
						 "vol: " .. tr.volume, "pan: " .. tr.pan )

			for kf, fx in opairs(tr.fx) do
					s = s .. sprint( "\tFX: ", fx.id, fx.name )

					for kp, param in opairs(fx.params) do
							s = s .. sprint( "\t\t Param " .. kp, param.name, param.curval )
					end
			end
	end
	print(s)
end

-- May be useful to make it into skinny html!

function TracksTable2Html(tt)
	-- row template stamper for Track row
	local function ths(tr)	-- track html string
		local th = "\t<tr>\r\n" ..
		"\t\t<td>Track:</td><td>trid</td><td><b>trname</b></td>\r\n" ..
		"\t\t<td>vol: trvolume</td>\r\n" ..
		"\t\t<td>pan: trpan</td>\r\n" ..
		"\t</tr>\r\n"

		th = string.gsub(th, "trid", tr.id)
		th = string.gsub(th, "trname", tr.name)
		th = string.gsub(th, "trvolume", tr.volume)
		th = string.gsub(th, "trpan", tr.pan)

		return th
	end

	-- row template stamper for Effect row
	local function fhs(fx)	-- fx html string
		local fh = "\t<tr>\r\n" ..
		"\t\t<td></td><td>Effect:</td><td>fxid</td>\r\n" ..
		"\t\t<td><b>fxname</b></td>\r\n" ..
		"\t</tr>\r\n"

		fh = string.gsub(fh, "fxid", fx.id)
		fh = string.gsub(fh, "fxname", fx.name)

		return fh
	end

	-- row template stamper for Param row
	local function phs(kp, param)	-- param html string
		ph = "\t<tr>\r\n" ..
		"\t\t<td></td><td></td>\r\n" ..
		"\t\t<td>Param:</td><td>kp</td>\r\n" ..
		"\t\t<td>paramname</td>\r\n" ..
		"\t\t<td>paramcurval</td>\r\n" ..
		"\t</tr>\r\n"

		ph = string.gsub(ph, "kp", kp)
		ph = string.gsub(ph, "paramname", param.name)
		ph = string.gsub(ph, "paramcurval", param.curval)

		return ph
	end

	-- begin accumulating our html
	local ah = '<table border="1">\r\n'

	for k,tr in opairs(tt) do
		local th = ths(tr)
		for kf, fx in opairs(tr.fx) do
			local fh = fhs(fx)
			for kp, param in opairs(fx.params) do
				local ph = phs(kp, param)
				fh = fh .. ph
			end
			th = th .. fh
		end
		ah = ah .. th
	end
	ah = ah .. "</table>"
	return ah
end

-- put it in a file

function TracksTable2HtmlFile(tt)
	local h = TracksTable2Html(tt)
	local ns = input("Save to:", "N:\\tracks.html")
	local f = io.open(ns, "wb")	-- 'b' so we control eols
	f:write(h)
	f:close()
end

function ShowTracksTable()
	require("wx")
	if wx == nil then return end
	local ht = TracksTable2Html(TracksTable())

	local frame = wx.wxFrame(	wx.NULL, wx.wxID_ANY, "Tracks",
								wx.wxDefaultPosition, wx.wxSize(1024, 768),
								wx.wxDEFAULT_FRAME_STYLE )

	frame:Connect(wx.wxID_EXIT, wx.wxEVT_COMMAND_MENU_SELECTED,
		function (event)
			frame:Close(true)
		end )


	local hp = wx.wxLuaHtmlWindow(frame)
	hp:SetPage(ht)
	frame:Show(true)
	return frame
end

--[[
dofile("w:\\ReaLua\\trunk\\examples.lua")
tt = TracksTable()
PrintTracksTable(tt)
print(TracksTable2Html(tt))
TracksTable2HtmlFile(tt)
]]

--[[ module-table for our variety of panning functions: ]]

pan = {}			-- the actual exposed "module" name

local setfenv = setfenv
local getfenv = getfenv
local math = math
local unpack = unpack
local reaper = reaper
local opairs = opairs
local trax = trax

setfenv(1, pan)

-- flat distribution

function random(...)
	for i,mt in opairs( trax(...) ) do
		local pv = math.random() * 2 - 1
		reaper.MediaTrackInfo.SetPan(mt, pv)
	end
end

-- left-to-right equal spacing

function even(...)
	local trt = trax(...)

	if #trt == 1 then
		local mt = unpack(trt)	-- get first and only element
		reaper.MediaTrackInfo.SetPan(mt, 0)
	else
		local interval = 2/(#trt-1)
		for i,mt in opairs(trt) do
			local pv = (i-1) * interval - 1
			reaper.MediaTrackInfo.SetPan(mt, pv)
		end
	end
end

-- other distributions can be programmed easily:

function crowd(...)
	-- roll twelve to approximate Gaussian:
	local function gauss12()
	  local sum = 0.0
	  for i = 1,12 do
		sum = sum + math.random()
	  end
	  return( (sum - 6.0) / 6.0 )
	end

	for i,mt in opairs( trax(...) ) do
		local pan = gauss12()
		reaper.MediaTrackInfo.SetPan(mt, pan)
	end
end

--  or not so easily

function spread(...)
	local trt = trax(...)

	if #trt == 1 then
		local mt = unpack(trt)	-- get first and only element
		reaper.MediaTrackInfo.SetPan(mt, 0)
	else
		local interval = math.pow( 2, 1/(#trt-1) )	-- root of two
		local mp = math.floor(#trt/2)
		for i = 1,mp do
			local pan = math.pow( interval, #trt - i ) - 1
			reaper.MediaTrackInfo.SetPan( trt[ i ], -pan)
			reaper.MediaTrackInfo.SetPan( trt[ #trt - i + 1 ], pan)
		end
		if math.mod( #trt, 2 ) == 1 then
			reaper.MediaTrackInfo.SetPan( trt[ mp+1 ], 0)
		end
	end
end

setfenv(1, getfenv(0))

--
-- end of pan module, everything following is in the global space _G

-- select only one track (this is not trax-aware)

function SelectOnly(tn)
	for i = 0, reaper.GetNumTracks() do
		local desel = reaper.CSurf_TrackFromID( i, false )
		reaper.SetTrackSelected( desel, false )
	end
	local sel = reaper.CSurf_TrackFromID( tn, false )
	reaper.SetTrackSelected( sel, true )
end

-- _Enums map nicely to Lua iterators:

-- iterate over the given reaper enum function

function ireanum(f)
	local function iter(f, i)
		i = i + 1
		local nxt, desc = f(i)
		if nxt ~= 0 then
			return i, desc
		end
	end

	return iter, f, -1
end

-- so we can use it in fors

function PrintSinks()
	for nxt, desc in ireanum(reaper.PCM_Sink_Enum) do
		print( "PCM_Sink " .. nxt, desc )
	end
end

function PrintPitchModes()
	-- iterate over Pitch Shift Modes
	local function imodes()
		local mdx = -1
		return function()
			mdx = mdx + 1
			local mode = reaper.EnumPitchShiftModes(mdx)
			if mode then
				return mdx, mode
			end
		end
	end
	-- iterate over Pitch Shift SubModes
	local function isubmodes(mdx)
		local sdx = -1
		return function()
			sdx = sdx + 1
			local submode = reaper.EnumPitchShiftSubModes(mdx, sdx)
			if submode then
				return sdx, submode
			end
		end
	end

	-- use iterators nestedly
	local s = ""
	for mdx, mode in imodes(reaper.EnumPitchShiftModes) do
		s = s .. sprint("PitShfMode " .. mdx, mode)
		for sdx, submode in isubmodes(mdx) do
			s = s .. sprint("\tPitSubMode " .. sdx, submode)
		end
	end
	print(s)
end

function PrintProjectMarkers()
	local function imarks()
		local mdx = -1
		return function()
			mdx = mdx + 1
			return reaper.EnumProjectMarkers(mdx)
		end
	end

	print(string.format("%8s %8s %8s %s", "Index", "Marker", "Position", "Name"))
	for tm in imarks() do
		print(string.format("%8d %8d %8.3f %s",
				tm.index, tm.markrgnindexnumber,
				tm.pos, tm.name))
	end
end

--[[
dofile("w:\\ReaLua\\trunk\\examples.lua")
PrintProjectMarkers()
PrintSinks()
PrintPitchModes()
]]

function PrintFileInfo( filename )
	ps = reaper.PCM_Source_CreateFromFile( filename )

	print("File Name:\t" .. ps:GetFileName() )
	print("Length (s):\t" .. ps:GetLength() )
	print("Channels:\t" .. ps:GetNumChannels() )
	print("Rate:\t\t" .. ps:GetSampleRate() )

	ps:Delete()
end

--[[ use the reaper.keystack(...) function to
 send keystrokes to the Reaper window.  You might want to
 use this instead of reaper.Main_OnCommand() as the keystrokes
 do not change as much as the command numbers do.  They are
 also easier to remember!
]]

function TogClock()
	reaper.keystack( reaper.VK_MENU, string.byte("C") ) -- ALT-C
end

function SaveAs()
	reaper.keystack( reaper.VK_CONTROL,
					 reaper.VK_MENU,
					 string.byte("S") ) -- CTRL-ALT-S
end

function quit()
	reaper.keystack( reaper.VK_CONTROL, string.byte("Q") )	-- CTRL-Q
end

-- http://shup.com/Shup/34485/reaper_command-identifiers.txt

-- use the reaper.Main_OnCommand(number, arg) function to
-- send commands to Reaper

function WrapCommand(number, arg)
	return function()
		reaper.Main_OnCommand(number, arg)
	end
end

-- they can be in the global space

SeeFX = WrapCommand(40291, 0)	-- Track: View FX chain for current track

-- wrapped commands in locals make things legibler!

function DriveNuts()
	local rewind = WrapCommand(40042, 0)	-- Transport: Go to start of project
	local play = WrapCommand(1007, 0)		-- Transport: Play
	local pause = WrapCommand(1008, 0)		-- Transport: Pause

	rewind()

	for i = 1,10 do
		play()
		reaper.sleep(math.random() * 1000)
		pause()
		reaper.sleep(math.random() * 1000)
	end
end

-- let's even call xome of Xenakios'ses commands!

xebase = 53003	-- this number changes depending on currently installed plugins

function xenafil()
	local function xecmd(cid)
		return WrapCommand( cid + xebase )
	end

	local choose = xecmd(17, 0)	-- Xenakios Extensions : Choose Files For Random Insert...
	local insert = xecmd(18, 0)	-- Xenakios Extensions : Insert Random File

	choose()
	insert()
end

-- play "scripts" of commands

function scripto(t)
	for k,v in opairs(t) do
		reaper.Main_OnCommand(v, 0)
	end
end

-- pick a random file to add in a new track
-- randomize all track colors, rewind and play

function PickNPlay()
	scripto{
		40297,  -- Track: Unselect all tracks
		17 + xebase,	-- Xenakios Extensions : Choose Files For Random Insert...
		18 + xebase,	-- Xenakios Extensions : Insert Random File
		40108,  -- Item Properties: Normalize items
		40296,  -- Track: Select all tracks
		40358,  -- Track: Set selected track colors to random colors
		40042,	-- Transport: Go to start of project
		1007	-- Transport: Play
	}
end

--[[ or the illegible, un-maintainable!:
scripto{40297, 53020, 53021, 40296, 40358, 40108, 40042, 1007}
]]

--[[
dofile("w:\\ReaLua\\trunk\\examples.lua")
TogClock()
SaveAs()
]]

function InsertMediaHardWay( filename )
  if reaper.GetNumTracks() < 1 then
	reaper.InsertTrackAtIndex( 1 , true )
  end

-- Okay we get a MediaTrack
	mt = reaper.CSurf_TrackFromID( 1 , false )
-- then we get a new MediaItem
	mi = reaper.AddMediaItemToTrack(mt)

-- this one has no MediaItem_Takeses so
	mit = reaper.AddTakeToMediaItem(mi)

	ps = reaper.PCM_Source_CreateFromFile( filename )
	reaper.MediaItemTakeInfo.SetSource(mit,ps)

	reaper.MediaItemInfo.SetLength(mi, ps:GetLength() )
end

-- sink = reaper.PCM_Sink_CreateMIDIFile("wah.mid", "", 0, 66, 5)

function PrintMidiInputs()
	for idx = 0, reaper.GetNumMIDIInputs() do
		local ns = reaper.GetMIDIInputName(idx)
		if ns then
			print("Midi In " .. idx, ns)
		end
	end
end

function PrintMidiOutputs()
	for idx = 0, reaper.GetNumMIDIOutputs() do
		local ns = reaper.GetMIDIOutputName(idx)
		if ns then
			print("Midi Out " .. idx, ns)
		end
	end
end

function PrintResampleModes()
	local function iresamplemodes()
		local mdx = -1
		return function()
			mdx = mdx + 1
			local ns = reaper.Resample_EnumModes(mdx)
			if ns then return mdx, ns end
		end
	end

	for idx, ns in iresamplemodes() do
		print("Resample Mode " .. idx, ns)
	end
end

function PrintInputChannelNames()
	local idx = -1
	repeat
		idx = idx + 1
		local ns = reaper.GetInputChannelName(idx)
		if ns then
			print("Input Channel " .. idx, ns)
		end
	until ns == nil
end

function PrintOutputChannelNames()
	local idx = -1
	repeat
		idx = idx + 1
		local ns = reaper.GetOutputChannelName(idx)
		if ns then
			print("Output Channel " .. idx, ns)
		end
	until ns == nil
end

--[[
dofile("w:\\ReaLua\\trunk\\examples.lua")
PrintMidiInputs()
PrintMidiOutputs()
PrintResampleModes()
PrintInputChannelNames()
PrintOutputChannelNames()
]]

function StartMidiRec( inid )
	mip = reaper.CreateMIDIInput(inid); -- print(mip)

	mip:start()
	mip:RunPreNoteTracking(1)

	return mip
end

function StopMidiRec( mip, ts )
	mip:stop()
	mip:SwapBufs(ts)
	mel = mip:GetReadBuf(); -- print(mel:GetSize())
	return mel
end

function MidiEvents(mel)
	local id = 0
	local ml = mel
	return function()
		local nxt, met = ml:EnumItems(id)
		id = id + 1
		if nxt ~= 0 then
			return id-1, met
		end
	end
end

function PrintMidiEvents(mel)
	for id, met in MidiEvents(mel) do
		print( "Midi Event " .. id, met )
	end
end

--[[
dofile("w:\\ReaLua\\trunk\\examples.lua")
PrintMidiInputs()
mn = tonumber( input("Enter a Midi Input number", "1") )
mi = StartMidiRec(mn)
sn = tonumber( input("Play something\r\nEnter a timestamp") )
ml = StopMidiRec( mi, sn )
PrintMidiEvents(ml)
]]

function propen()
  require('lfs')
  if lfs then
    p = "Open Project In\r\n" .. lfs.currentdir()
    reaper.Main_openProject( input( p, "rowboat.rpp" ) )
    reaper.CSurf_OnPlay()
    input( "Hit Return to Stop It!" )
    reaper.CSurf_OnStop()
  end
end

--[[
dofile("w:\\ReaLua\\trunk\\examples.lua")
tt = TracksTable()
PrintTracksTable(tt)
PrintSinks()
PrintPitchModes()
PrintMidiInputs()
PrintMidiOutputs()
PrintResampleModes()
PrintInputChannelNames()
PrintOutputChannelNames()
]]


-- lua for midi heppers

-- takes a Lua number and returns
-- a table of its 7-bit wyrds

function turn7(num)
	local t = {}

	while num >= 0x80 do		-- does not fit in seven bits
		local low7 = num % 0x80		-- isolate the low 7 bits
--		print("\n" , low7)
		table.insert(t, 1, low7)

		local rest = num - low7		-- "mask" out low 7 bits
		num = rest / 0x80 		-- shift right 7 bits
	end
	table.insert(t, 1, num)			-- the last low seven bits

	return t
end

function check7(num)
	local t = turn7(num)
	local sum = 0
	for k,v in opairs(t) do
		sum = sum + v * 128 ^ (#t - k)
	end
	print("num: " .. num .. " sum: " .. sum .. " equal " .. tostring(num == sum):upper())
end

-- returns a Lua string containing
-- a MIDI "variable-length" value

function evl(num)
	local tbl = turn7(num)

	for i=1, # tbl - 1 do
		tbl[i] = tbl[i] + 0x80	-- set high bit means "byte(s) to follow"
	end

	return string.char( unpack(tbl) )
end

function playNote(nn)
	local nnn = nn or 0x7F
	local mo = reaper.CreateMIDIOutput(3,false)


	local non = reaper.makeMidiEvent(0, 0x90, nnn, 0x7F)
	local nof = reaper.makeMidiEvent(0, 0x80, nnn, 0x7F)
	local mel = reaper.makeMidiEventList()
	mel:AddItem(non)
	mel:AddItem(nof)
end
